#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define LEDNAME  "gpioled"
#define LEDCOUNT    1

struct gpio_dev {
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;

    struct device_node *nd;
    int led_gpio;
};
struct gpio_dev gpioled;

int led_open (struct inode *node, struct file *filep)
{
    filep->private_data = &gpioled;
    return 0;
}

ssize_t led_write (struct file *filep, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    unsigned char databuf[1];
    struct gpio_dev *dev = (struct gpio_dev *)filep->private_data;

    ret = __copy_from_user(databuf, buf, 1);
    if(ret < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    if(databuf[0] != 0)  //打开 led
    {
        gpio_set_value(dev->led_gpio, 0);
    }
    else  //关闭 led
    {
        gpio_set_value(dev->led_gpio, 1);
    }

    return size;
}

int led_release (struct inode *node, struct file *filep)
{
    struct gpio_dev *dev = filep->private_data;
    filep->private_data = NULL;

    return 0;
}

struct file_operations led_operations = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
    .release = led_release,
};

/**
 * 模块入口
 */
static int __init led_init(void)
{
    int ret;

    gpioled.major = 0;

    // 获取设备号
    if(gpioled.major)  //指定设备号
    {
        ret = register_chrdev_region(gpioled.devid, LEDCOUNT, LEDNAME);
    }
    else  //未指定主设备号，由系统自动分配
    {
        ret = alloc_chrdev_region(&gpioled.devid, 0, LEDCOUNT, LEDNAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    if(ret < 0)
    {
        goto fail_devid;
    }
    printk("gpioled.major = %d, gpioled.minor = %d\r\n", gpioled.major, gpioled.minor);

    // 添加设备
    cdev_init(&gpioled.cdev, &led_operations);
    ret = cdev_add(&gpioled.cdev, gpioled.devid, LEDCOUNT);
    if (ret < 0)
    {
        goto fail_cdev;
    }
    
    // 添加类
    gpioled.class = class_create(THIS_MODULE, LEDNAME);
    if(IS_ERR(gpioled.class))
    {
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }

    // 添加设备
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, LEDNAME);
    if(IS_ERR(gpioled.device))
    {
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }

    // 1、获取设备节点
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL)
    {
        ret = -EIO;
        goto fail_node;
    }

    // 2、获取LED所对应的GPIO
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if(gpioled.led_gpio < 0)
    {
        printk("Can't find led gpio!\r\n");
        ret = -EIO;
        goto fail_node;
    }
    printk("led gpio num = %d\r\n", gpioled.led_gpio);

    // 3、申请IO
    ret = gpio_request(gpioled.led_gpio, "led-gpios");
    if(ret < 0)
    {
        printk("request led gpio faild!\r\n");
        return -ENOSYS;
    }

    // 4、使用 IO，设置为输出
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0)
    {
        printk("Set led gpio output faild!\r\n");
        goto fail_setoutput;
    }

    // 5、输出低电平，点亮LED灯
    gpio_set_value(gpioled.led_gpio, 0);


    return 0;

fail_setoutput:
    gpio_free(gpioled.led_gpio);
fail_node:
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, LEDCOUNT);
fail_devid:
    return ret;
}

/**
 * 模块出口
 */
static void __exit led_exit(void)
{
    gpio_set_value(gpioled.led_gpio, 1);  //关闭led

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, LEDCOUNT);

    gpio_free(gpioled.led_gpio);
}

/**
 * 注册模块入口和出口
 */
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("george <g_yubo@163.com>");